
/*
* An OpenGL template/framework file for the Computer Graphics course
* at the University of Groningen.
*/

// If windows is used, windows.h should be included (before gl.h and glu.h)
#if defined(_WIN32)
#include <windows.h>
#endif

// If you use GLUT you do not need to include gl.h and glu.h
// as glut.h guarantees that gl.h and glu.h are properly 
// included.
// So if you do not wish to use  glut.h, uncomment the following lines.
//#include <GL/gl.h>
//#include <GL/glu.h>

#if defined(NEED_GLEW)
#include "glew.h"
#endif

// Apparently Mac OS X puts the GLUT headers somewhere different.
// For windows we use freeglut.
#if defined(__APPLE__)&& defined(__MACH__)
#include <GLUT/glut.h>
#elif defined(_WIN32)
#include <GL/freeglut.h>
#else
#include <GL/glut.h>
#endif



#include <stdlib.h>
#include <stdio.h>
#include <math.h>


GLfloat cubeVBOVertices[] = {
  -1,-1, 1,	-1, 1, 1,	1, 1, 1 ,	1,-1, 1, 
  -1,-1,-1, 	-1, 1,-1,	1, 1,-1,	1,-1,-1,
  -1,-1,-1,	-1,-1, 1,	-1, 1, 1,	-1, 1,-1,
  1,-1,-1, 	1,-1, 1,	1, 1, 1 ,	1, 1,-1,
  -1, 1,-1,	1, 1,-1,	1, 1, 1,	-1, 1, 1,
  -1,-1,-1,	-1,-1, 1, 	1,-1, 1,	1,-1,-1
};

GLfloat cubeColor[] = {1.0f,0.65f,0.0f, 1.0f,0.65f,0.0f, 1.0f,0.65f,0.0f, 1.0f,0.65f,0.0f, 
1.0f,0.0f,0.0f,1.0f,0.0f,0.0f, 1.0f,0.0f,0.0f, 1.0f,0.0f,0.0f,  
0.0f,0.0f, 1.0f, 0.0f,0.0f, 1.0f, 0.0f,0.0f, 1.0f, 0.0f,0.0f, 1.0f, 
0.0f,1.0f, 0.0f,0.0f,1.0f, 0.0f,0.0f,1.0f, 0.0f,0.0f,1.0f, 0.0f,
1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,
1.0f,1.0f,0.0f,1.0f,1.0f,0.0f,1.0f,1.0f,0.0f,1.0f,1.0f,0.0f};

GLfloat cubeNormal[] = {
  0,0,1,	0,0,1,	0,0,1,	0,0,1,
  0,0,-1,  0,0,-1,	0,0,-1,	0,0,-1,
  -1,0,0,	-1,0,0,	-1,0,0,	-1,0,0,
  1,0,0,	1,0,0,	1,0,0,	1,0,0,
  0,1,0,	0,1,0,	0,1,0,	0,1,0,
  0,-1,0,	0,-1,0,	0,-1,0,	0,-1,0
};

GLfloat currYaw = 0.0;
GLfloat currPitch = 0.0;
GLfloat currX = 0.0, currY = 0.0, currZ = 0.0;


GLfloat preEyeX = 0.0;
GLfloat preEyeY = 0.0;
GLfloat preEyeZ = 5.0;

GLfloat preLenX = 0.0;
GLfloat preLenY = 0.0;
GLfloat preLenZ = 0.0;

GLfloat preUpVectorX = 0.0;
GLfloat preUpVectorY = 1.0;
GLfloat preUpVectorZ = 0.0;

int preX_r = 0;
int preY_r = 0;


#define WIN_INIT_W 800
#define WIN_INIT_H 600
#define INIT_FOVY 60.0

int preY_zoom = 0;
GLdouble preFovy = INIT_FOVY;
int currW = WIN_INIT_W;
int currH = WIN_INIT_H;

// implement the Vertex buffer object
void drawCube(void)
{
  GLuint vboId;                              // ID of VBO
 
  // generate a new VBO and get the associated ID
  glGenBuffersARB(1, &vboId);
  
  // bind VBOs with IDs and set the buffer offsets of the bound VBOs
  // When buffer object is bound with its ID, all pointers in gl*Pointer()
  // are treated as offset instead of real pointer.
  
  glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
  glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(cubeVBOVertices)+sizeof(cubeColor)+sizeof(cubeNormal), 0, GL_STATIC_DRAW_ARB);
  glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, sizeof(cubeVBOVertices), cubeVBOVertices);                           
  glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, sizeof(cubeVBOVertices), sizeof(cubeColor), cubeColor);  // copy colours
  glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, sizeof(cubeVBOVertices)+sizeof(cubeColor), sizeof(cubeNormal), cubeNormal); 
  
  
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  
    
  // enable vertex arrays
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  glEnableClientState(GL_VERTEX_ARRAY);
  
  // before draw, specify vertex and index arrays with their offsets
  
  glNormalPointer(GL_FLOAT, 0, (void *)(sizeof(cubeVBOVertices) + sizeof(cubeColor)));
  glColorPointer(3, GL_FLOAT, 0, (void*)(sizeof(cubeVBOVertices)));
  glVertexPointer(3, GL_FLOAT, 0, 0);
  
  glDrawArrays(GL_QUADS, 0, 24);
  
  glDisableClientState(GL_VERTEX_ARRAY);  // disable vertex arrays
  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  
  /* Clear all pixels */
  glutSwapBuffers();   
}




void display(void)
{
  glLoadIdentity();

  gluLookAt(preEyeX,preEyeY, preEyeZ, preLenX, preLenY, preLenZ, preUpVectorX, preUpVectorY, preUpVectorZ);
  
  // rotate by X axis
  glRotatef(currPitch, 1.0, 0.0, 0.0);
  
  // rotate by Y axis
  glRotatef(currYaw, 0.0, 1.0, 0.0);
  
  // translate by x
  glTranslatef(currX, currY, currZ);
  
  drawCube();
}


int rotationMode = 0;
int zoomMode = 0;

#define UPDATE_EYE_F 0
#define UPDATE_EYE_B 1
#define UPDATE_EYE_L 2
#define UPDATE_EYE_R 3

GLfloat normalize(GLfloat x, GLfloat y, GLfloat z)
{
  return sqrt(x*x+y*y+z*z);
}

void updateEye(int dir)
{
  // Vector a: lens direction
  // Vector b: Up Vector
  // Vector r: right vector, get it by calculating cross multiply
  
#if 1
  GLfloat aX = preLenX - preEyeX;
  GLfloat aY = preLenY - preEyeY;
  GLfloat aZ = preLenZ - preEyeZ;
  
  GLfloat normalFront = normalize(aX, aY, aZ);
  
  GLfloat frontX = aX / normalFront;
  GLfloat frontY = aY / normalFront;
  GLfloat frontZ = aZ / normalFront;
  
  GLfloat bX = preUpVectorX - preEyeX;
  GLfloat bY = preUpVectorY - preEyeY;
  GLfloat bZ = preUpVectorZ - preEyeZ;
     
  GLfloat rX = aY * bZ - aZ * bY;
  GLfloat rY = aZ * bX - aX * bZ;
  GLfloat rZ = aX * bY - aY * bX;
  
  GLfloat normalRight = normalize(rX, rY, rZ);
   
  GLfloat rightX = rX / normalRight;
  GLfloat rightY = rY / normalRight;
  GLfloat rightZ = rZ / normalRight;
  
  switch (dir){
    case UPDATE_EYE_F:
      preEyeX += 0.1 * frontX;
      preEyeY += 0.1 * frontY;
      preEyeZ += 0.1 * frontZ;
     
      preLenX += 0.1 * frontX;
      preLenY += 0.1 * frontY;
      preLenZ += 0.1 * frontZ;     
      
      preUpVectorX += 0.1 * frontX;
      preUpVectorY += 0.1 * frontY;
      preUpVectorZ += 0.1 * frontZ;         
      
      break;
    case UPDATE_EYE_B:
      preEyeX -= 0.1 * frontX;
      preEyeY -= 0.1 * frontY;
      preEyeZ -= 0.1 * frontZ;
      
      preLenX -= 0.1 * frontX;
      preLenY -= 0.1 * frontY;
      preLenZ -= 0.1 * frontZ;     
      
      preUpVectorX -= 0.1 * frontX;
      preUpVectorY -= 0.1 * frontY;
      preUpVectorZ -= 0.1 * frontZ;          
      break;
    case UPDATE_EYE_R:

      preEyeX += 0.1 * rightX;
      preEyeY += 0.1 * rightY;
      preEyeZ += 0.1 * rightZ;
      
      preLenX += 0.1 * rightX;
      preLenY += 0.1 * rightY;
      preLenZ += 0.1 * rightZ;     
      
      //preUpVectorX += 0.1 * rightX;
      //preUpVectorY += 0.1 * rightY;
      //preUpVectorZ += 0.1 * rightZ;          
      
      printf("%f %f %f %f %f %f %f %f %f\n", preEyeX, preEyeY, preEyeZ, preLenX, preLenY, preLenZ, preUpVectorX, preUpVectorY, preUpVectorZ);

      break;
    case UPDATE_EYE_L:

      preEyeX -= 0.1 * rightX;
      preEyeY -= 0.1 * rightY;
      preEyeZ -= 0.1 * rightZ;
      
      preLenX -= 0.1 * rightX;
      preLenY -= 0.1 * rightY;
      preLenZ -= 0.1 * rightZ;     
      
      //preUpVectorX -= 0.1 * rightX;
      //preUpVectorY -= 0.1 * rightY;
      //preUpVectorZ -= 0.1 * rightZ; 
      
      printf("%f %f %f %f %f %f %f %f %f\n", preEyeX, preEyeY, preEyeZ, preLenX, preLenY, preLenZ, preUpVectorX, preUpVectorY, preUpVectorZ);

      break;  
  }
  display();
#else

  GLdouble fovy;
  
  switch (dir){
    case UPDATE_EYE_F:
      // adjust fovy according to the Y value change of the mouse
      fovy = preFovy - 1;
      fovy = (fovy > 170.0) ? 170 : (fovy < 10.0) ? 10.0 : fovy;
	  
      // change fovy to get zoom-in/out
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      gluPerspective(fovy,(GLdouble)currW/(GLdouble)currH,1.5,20.0);
      glMatrixMode(GL_MODELVIEW);
      drawCube();

      // update fovy
      preFovy = fovy;      
      break;
    case UPDATE_EYE_B:
      fovy = preFovy + 1;
      fovy = (fovy > 170.0) ? 170 : (fovy < 10.0) ? 10.0 : fovy;
      
      // change fovy to get zoom-in/out
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      gluPerspective(fovy,(GLdouble)currW/(GLdouble)currH,1.5,20.0);
      glMatrixMode(GL_MODELVIEW);
      drawCube();

      // update fovy
      preFovy = fovy;
      break;
    case UPDATE_EYE_L:
      currX -= 0.1;
      display();
      break;
    case UPDATE_EYE_R:
      currX += 0.1;
      display();
      break;
  }
#endif
}


void keyboard(unsigned char key, int x, int y)
{
  
  switch (key) {
    case 'q':
    case 'Q':
    case 27: // ESC key
      printf("Exiting...\n");
      exit(0);
      break;
    case 'w':
      updateEye(UPDATE_EYE_F);
      break;
case 's':
      updateEye(UPDATE_EYE_B);
      break;
case 'a':
      updateEye(UPDATE_EYE_L);
      break;
case 'd':
      updateEye(UPDATE_EYE_R);
      break;      
  }
}


void mouse(int button, int state, int x, int y)
{
  switch (button) {
    case GLUT_LEFT_BUTTON:
      switch (state) {
	case GLUT_DOWN:
	  // enter rotation mode
	  rotationMode = 1;
	  preX_r = x;
	  preY_r = y;
	  
	  // check if ctrl key press, then enter zoom mode
	  if (glutGetModifiers() == GLUT_ACTIVE_CTRL) {
	    zoomMode = 1;
	    preY_zoom = y;
	  } else {
	    zoomMode = 0;
	  }
	  break;
	  
	case GLUT_UP:
	  // exit rotation mode
	  rotationMode = 0;
	  preX_r = 0;
	  preY_r = 0;
	  break;
      }
      break;
	case GLUT_RIGHT_BUTTON:
	  // no action when right button press/release
	  break;
  }
  
  return;
}


void rotateObj(int x, int y)
{
  GLfloat yaw, pitch;
  
  if (!rotationMode) 
    return;
  
  // yaw corresponds Y axis rotation(right hand)
  yaw = x - preX_r;
  
  // pitch correspond X axis rotation(right hand)  
  pitch = y - preY_r;
  
  // rotate X axis
  glRotatef(pitch, 1.0, 0.0, 0.0);
  
  // rotate Y axis
  glRotatef(yaw, 0.0, 1.0, 0.0);
  
  // Redraw
  drawCube();
  
  // record current mouse coordinate
  preX_r = x;
  preY_r = y;
  
  // record current pitch and yaw
  currYaw += yaw;
  currPitch += pitch;
}

void zoomChange(int x, int y)
{
  // adjust fovy according to the Y value change of the mouse
  GLdouble fovy = preFovy - (y - preY_zoom);
  fovy = (fovy > 170.0) ? 170 : (fovy < 10.0) ? 10.0 : fovy;
  
  // change fovy to get zoom-in/out
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(fovy,(GLdouble)currW/(GLdouble)currH,1.5,20.0);
  glMatrixMode(GL_MODELVIEW);
  drawCube();
  
  // update y value
  preY_zoom = y;
  
  // update fovy
  preFovy = fovy;
}



void motion(int x, int y)
{
  // no rotate and no zoom, just return
  if (!rotationMode && !zoomMode)
    return;
  
  // zoom has higher priority
  if (zoomMode) {
    zoomChange(x, y);
    return;
  }
  
  // rotate has lower priority
  if (rotationMode) {
    rotateObj(x,y);
    return;
  }
  
  // if not zoom and rotate, FPS look around
  //lookAround(x,y);
  
  return;
}

void reshape(int w, int h)
{
  currW = w;
  currH = h;
  
  glViewport(0,0, (GLsizei) w, (GLsizei) h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(INIT_FOVY,(GLdouble)w/(GLdouble)h,1.5,20.0);
  glMatrixMode(GL_MODELVIEW);
}

void init()
{


  return;
}

int main(int argc, char** argv)
{
  #if defined(NEED_GLEW)
  GLenum err;
  #endif
  
  
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(800,600);
  glutInitWindowPosition(220,100);
  glutCreateWindow("Computer Graphics Assignment 1 - by Zhe Sun");
  
  #if defined(NEED_GLEW)
  /* Init GLEW if needed */
  err = glewInit();
  if (GLEW_OK != err) {
    /* Problem: glewInit failed, something is seriously wrong. */
    fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
    exit(1);
  }
  fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
  #endif
  
  /* Select clearing (background) color */
  glClearColor(0.0,0.0,0.0,0.0);
  glShadeModel(GL_FLAT);
  glEnable(GL_DEPTH_TEST);
 
  init();
  
  /* Register GLUT callback functions */
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutReshapeFunc(reshape);
  
  glutMainLoop();
  
  return 0;
}
